package com.klondike.common;

import java.lang.reflect.Array;
import java.net.URL;
import java.security.CodeSource;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.TimeUnit;

import com.klondike.common.concurrent.MutuxPool;
import com.klondike.common.concurrent.SimpleMutuxPool;

public class Utils {

    private Utils() {
    }

    public static boolean isNullOrBlank(String value) {
        return value == null || value.length() == 0;
    }

    public static String trimToNull(String value) {
        if (value != null) {
            value = value.trim();
        }
        return isNullOrBlank(value) ? null : value;
    }

    public static boolean hasValue(String value) {
        return !isNullOrBlank(value);
    }

    public static boolean notNull(String value) {
        return trimToNull(value) != null;
    }

    /**
     * @param number
     *            the number to be search in numbers.
     * @param numbers
     *            the numbers
     * @return true if numbers has element same as number.
     */
    public static boolean in(int number, int[] numbers) {
        if (numbers == null || numbers.length == 0) {
            return false;
        }

        int len = numbers.length / 2 + 1;
        for (int i = 0; i < len;) {
            if (number == numbers[i]) {
                return true;
            }

            i++;

            if (number == numbers[numbers.length - i]) {
                return true;
            }
        }

        return false;
    }

    /**
     * @param value
     *            the value to be search in values.
     * @param values
     *            the values
     * @return true if values has any element same as value.
     */
    public static boolean in(Object value, Object[] values) {
        if (isEmpty(values)) {
            return false;
        }

        int len = values.length / 2 + 1;
        for (int i = 0; i < len;) {
            if (equals(value, values[i])) {
                return true;
            }

            i++;

            if (equals(value, values[values.length - i])) {
                return true;
            }
        }

        return false;
    }

    public static boolean isEmpty(Object o) {
        if (o == null) {
            return true;
        }
        if (o.getClass().isArray()) {
            return Array.getLength(o) == 0;
        }
        if (o instanceof Collection<?>) {
            return ((Collection<?>) o).size() == 0;
        }
        if (o instanceof Map<?, ?>) {
            return ((Map<?, ?>) o).size() == 0;
        }

        if (o instanceof CharSequence)
            return ((CharSequence) o).length() == 0;

        return false;
    }

    public static boolean isEmpty(Object[] values) {
        return values == null || values.length == 0;
    }

    public static boolean isEmpty(Collection<?> values) {
        return values == null || values.size() == 0;
    }

    /**
     * Compare two objects for their equality. It returns true if both instances
     * are null or equals.
     * 
     * @param o1
     *            first instance
     * @param o2
     *            second instance
     * @return true if both object are equals. Two null instances are also
     *         equals.
     */
    public static boolean equals(Object o1, Object o2) {
        return o1 == o2 ? true : o1 == null ? false : o2 == null ? false : o1.equals(o2);
    }

    public static int hashCode(Object o) {
        return o == null ? 0 : o.hashCode();
    }

    public static <V extends Comparable<? super V>> int compare(V o1, V o2) {
        return o1 == o2 ? 0 : (o1 == null ? -1 : o2 == null ? 1 : o1.compareTo(o2));
    }

    public static <E extends Comparable<?>> Set<E> newTreeSet(E... items) {
        Set<E> set = new TreeSet<E>();
        if (!isEmpty(items)) {
            for (E e : items) {
                set.add(e);
            }
        }

        return set;
    }

    /**
     * Compares the passed collection of {@code Comparable<T>}.
     * 
     * @param <T>
     *            type of the elements of the collections
     * @param o1
     *            first collection.
     * @param o2
     *            second collection.
     * @return -1, 0 or 1
     */
    public static <T extends Comparable<? super T>> int compare(Collection<T> o1, Collection<T> o2) {
        if (o1 == o2) {
            return 0;
        }

        int cmp = 0;
        Iterator<T> i1 = o1.iterator(), i2 = o2.iterator();

        while (i1.hasNext() && i2.hasNext()) {
            cmp = i1.next().compareTo(i2.next());
            if (cmp != 0) {
                break;
            }
        }
        if (cmp == 0) {
            if (i1.hasNext()) {
                cmp = 1;
            }
            if (i2.hasNext()) {
                cmp = -1;
            }
        }

        return cmp;
    }

    public static Properties subsetOf(Properties properties, String namePrefix) {
        return subsetOf(properties, namePrefix, true);
    }

    /**
     * @param properties
     *            the properties
     * @param namePrefix
     *            the property name prefix
     * @param stripPrefix
     *            true if prefix needs to be removed.
     * @return the subset of the properties.
     */
    public static Properties subsetOf(Properties properties, String namePrefix, boolean stripPrefix) {
        Properties subset = new Properties();
        for (Map.Entry<Object, Object> e : properties.entrySet()) {
            String prop = String.valueOf(e.getKey());
            String val = String.valueOf(e.getValue());
            if (prop.startsWith(namePrefix)) {
                if (stripPrefix) {
                    prop = prop.substring(namePrefix.length());
                }

                subset.setProperty(prop, val);
            }
        }

        return subset;
    }

    public static <E1, E2> SimpleEntry<E1, E2> newPair(E1 e1, E2 e2) {
        return new ImmutableSimpleEntry<E1, E2>(e1, e2);
    }

    public static <T> T ifNull(T o, T alternate) {
        return o == null ? alternate : o;
    }

    public static String ifNullOrBlank(String o, String alternate) {
        return isNullOrBlank(o) ? alternate : o;
    }

    public static void checkCondition(boolean condition, String msg) {
        if (condition == false) {
            throw new IllegalArgumentException(msg);
        }
    }

    public static String normalizeText(String text) {
        if (hasValue(text)) {
            StringBuilder sb = new StringBuilder(text.length());
            boolean lastWhite = false;
            boolean ignore = false;
            for (int i = 0; i < text.length(); i++) {
                char ch = text.charAt(i);
                boolean isWhiteChar = (Character.isWhitespace(ch));
                if (isWhiteChar) {
                    if (!ignore && lastWhite) {
                        continue;
                    }
                    ch = ' '; // fix NL, LF, FF etc. chars
                    lastWhite = true;
                } else
                    lastWhite = false;

                ignore ^= (ch == '\'');

                sb.append(ch);
            }

            return sb.toString().trim();
        }
        return "";
    }

    public static void checkNull(Object o, String msg) {
        if (o == null)
            throw new IllegalArgumentException(msg);
    }

    public static String[] parseCsvLine(String line) {
        line = trimToNull(line);
        List<String> tokenList = new ArrayList<String>();
        StringBuilder sb = new StringBuilder(30);
        if (hasValue(line)) {
            final char SEPARATOR_CHAR = ',';
            final char QUOTE_CHAR = '"';
            int index = 0;
            char ch = ' ';
            boolean inQuote = false;
            boolean wasInQuote = false;
            while (index < line.length()) {
                ch = line.charAt(index);
                if (inQuote) {
                    if (ch == QUOTE_CHAR) {
                        if (lookAHeadChar(line, index) == QUOTE_CHAR) {
                            sb.append(ch);
                            index++;// consume next QUOTE as well.
                        } else {
                            inQuote = false;
                            int next = index + 1;
                            while (next < line.length()) {
                                if (!Character.isWhitespace(line.charAt(next))) {
                                    if (line.charAt(next) != SEPARATOR_CHAR) {
                                        throw new IllegalArgumentException("separate char was expected.");
                                    }
                                    break;
                                } else {
                                    ++index;
                                }
                                next = index + 1;
                            }
                            wasInQuote = true;
                        }
                    } else {
                        sb.append(ch);
                    }
                } else {
                    if (ch == QUOTE_CHAR) {
                        inQuote = true;
                    } else {
                        if (ch == SEPARATOR_CHAR) {
                            String token = sb.toString();
                            sb.setLength(0);
                            if (!wasInQuote) {
                                token = token.trim();
                            }
                            tokenList.add(token);
                            wasInQuote = false;
                        } else {
                            sb.append(ch);
                        }
                    }
                }

                index++;
            }
            if (inQuote) {
                throw new IllegalArgumentException("unclosed quote");
            }

            String token = sb.toString();
            // grab last token
            if (!wasInQuote) {
                token = token.trim();
            }

            tokenList.add(token);
        }

        return tokenList.toArray(new String[tokenList.size()]);
    }

    private static char lookAHeadChar(String line, int index) {
        char ch = '\0';
        if (++index < line.length()) {
            ch = line.charAt(index);
        }
        return ch;
    }

    public static <T> Collection<T> asCollection(Iterator<T> itr) {
        Collection<T> coll = new ArrayList<T>();
        while (itr.hasNext()) {
            coll.add(itr.next());
        }

        return coll;
    }

    public static URL getSourceUrlFor(Class<?> givenClass) {
        ProtectionDomain protectionDomain = givenClass.getProtectionDomain();
        CodeSource codeSource = protectionDomain.getCodeSource();

        return codeSource.getLocation();
    }

    public static <T> MutuxPool<T> newMutuxPool() {
        return newMutuxPool(Integer.MAX_VALUE, Integer.MAX_VALUE, TimeUnit.SECONDS, 16, 32, 4);
    }

    public static <T> MutuxPool<T> newMutuxPool(long lockAutoExpriryTimeout, long lockMaxWaitTimeout,
            TimeUnit timeUnit, int segmentInitialCapacity, int segmentThreashold, int concurrencyLevel) {
        return new SimpleMutuxPool<T>(lockAutoExpriryTimeout, lockMaxWaitTimeout, timeUnit, segmentInitialCapacity,
                segmentThreashold, concurrencyLevel);
    }
}
